{
 "cells": [
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> Project page is [Keyboard as a Python Code](https://hackaday.io/project/188907-keyboard-as-a-python-code)\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Make a case\n",
    "\n",
    "![target case](../imgs/exported_case.png)\n",
    "\n",
    "Modeling with cadquery to create a case that can be 3D printable.\n",
    "\n",
    "Since cadquery viewer is not available on google colaboratory due to OS, the notebook is opened with binder.   \n",
    "The binder enables jupyter notebook to be used by preconfiguring the OS and necessary libraries using Docker.   \n",
    "Therefore, there is no need to install any libraries."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# What is cadquery?\n",
    "[cadquery](https://github.com/CadQuery/cadquery) is an environment for coding python to generate 3D models.\n",
    "Use [jupyter_cadquery](https://github.com/bernhard-42/jupyter-cadquery) as a viewer.\n",
    "\n",
    "- cadquery https://github.com/CadQuery/cadquery\n",
    "- cadquery documentation https://cadquery-ja.readthedocs.io/ja/latest/\n",
    "- jupyter-cadquery https://github.com/bernhard-42/jupyter-cadquery\n",
    "\n",
    "Run the cell below and make sure the viewer opens.\n",
    "\n",
    "To run a cell, click on it and press `Shift + Enter` or `▶` on the top menu"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import cadquery as cq\n",
    "\n",
    "from jupyter_cadquery import (\n",
    "    versions,\n",
    "    show, PartGroup, Part, \n",
    "    get_viewer, close_viewer, get_viewers, close_viewers, open_viewer, set_defaults, get_defaults, open_viewer,\n",
    "    get_pick,\n",
    ")\n",
    "\n",
    "from jupyter_cadquery.replay import replay, enable_replay, disable_replay\n",
    "\n",
    "enable_replay(False)\n",
    "\n",
    "set_defaults(\n",
    "    cad_width=640, \n",
    "    height=480, \n",
    ")\n",
    "\n",
    "print()\n",
    "versions()\n",
    "\n",
    "cv = open_viewer(\"CadQuery\", anchor=\"right\")"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Try cadquery\n",
    "Generates a simple cube and displays it in the viewer."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Make a cube from the xy plane and fillet the edges.\n",
    "box = cq.Workplane('XY').box(1, 2, 3).edges().fillet(0.1)\n",
    "# Display in default viewer\n",
    "show(box)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Design the case\n",
    "Design a tray-type case in which PCBs are screwed in place.\n",
    "\n",
    "## Rough shape\n",
    "Create a rough outline based on the shape of the PCB. As usual, declare and use constants."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# PCB Outline\n",
    "PCB_WIDTH = 76.0\n",
    "PCB_HEIGHT = 57.0\n",
    "PCB_THICKNESS = 1.6\n",
    "\n",
    "# Z margin between case and PCB\n",
    "CASE_MARGIN_TOP = 11.0\n",
    "CASE_MARGIN_BOTTOM = 3.5\n",
    "\n",
    "# XY margin between case and PCB\n",
    "CASE_MARGIN_PCB = 0.5\n",
    "\n",
    "# Case Thickness\n",
    "CASE_FRAME = 2.0\n",
    "CASE_BOTTOM = 3.0\n",
    "\n",
    "INNER_HEIGHT = CASE_MARGIN_TOP + PCB_THICKNESS + CASE_MARGIN_BOTTOM\n",
    "CASE_HEIGHT = INNER_HEIGHT + CASE_BOTTOM\n",
    "\n",
    "# Draw a rectangle based on the XY plane and extrude it\n",
    "case = (\n",
    "    cq.Workplane(\"XY\")\n",
    "    .rect(\n",
    "        PCB_WIDTH + (CASE_FRAME + CASE_MARGIN_PCB) * 2,\n",
    "        PCB_HEIGHT + (CASE_FRAME + CASE_MARGIN_PCB) * 2,\n",
    "    )\n",
    "    .extrude(CASE_BOTTOM + INNER_HEIGHT)\n",
    "    .edges(\"|Z\")\n",
    "    .fillet(2)\n",
    "    .edges(\"|X\")\n",
    "    .chamfer(1)\n",
    ")\n",
    "\n",
    "show(case)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Cut out the inside of the case\n",
    "Cut out the inside of the case to make a tray shape.   \n",
    "Begin by selecting the reference plane from the current case face"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Reference the topmost surface on the Z-axis from the case faces.\n",
    "# Draw a rectangle and cut off the inside height\n",
    "case = (\n",
    "    case.faces(\">Z\")\n",
    "    .workplane()\n",
    "    .rect(PCB_WIDTH + CASE_MARGIN_PCB * 2, PCB_HEIGHT + CASE_MARGIN_PCB * 2)\n",
    "    .cutBlind(-INNER_HEIGHT)\n",
    ")\n",
    "\n",
    "show(case)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Screw bosses and holes\n",
    "The reference plane is saved and used with `tag(\"name\")`.   \n",
    "Prepare an array of screw position coordinates in advance and repeat the same process."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Screw coordinates\n",
    "SCREW_POINTS = [(19, 9.5), (19, -9.5), (-19, -9.5), (0, 9.5)]\n",
    "\n",
    "# Make the base plane by selecting the top plane on the z-axis (the edge of the case that is just barely left) from the face of the case and offsetting it down by `INNER_HEIGHT`.\n",
    "# Make a cylinder of radius 2.5mm at each coordinate\n",
    "# Make a hole of radius 1.1mm at each coordinate\n",
    "# Make a new reference plane offset 1mm down from the base plane and drill a square hole to the bottom (a hole to put a nut)\n",
    "case = (\n",
    "    case.faces(\">Z\")\n",
    "    .workplane(offset=-INNER_HEIGHT)\n",
    "    .tag(\"InnerBottom\")\n",
    "    .pushPoints(SCREW_POINTS)\n",
    "    .circle(2.5)\n",
    "    .extrude(CASE_MARGIN_BOTTOM)\n",
    "    .workplaneFromTagged(\"InnerBottom\")\n",
    "    .pushPoints(SCREW_POINTS)\n",
    "    .circle(1.1)\n",
    "    .cutThruAll()\n",
    "    .workplaneFromTagged(\"InnerBottom\")\n",
    "    .workplane(offset=-1)\n",
    "    .pushPoints(SCREW_POINTS)\n",
    "    .rect(4.2, 4.8)\n",
    "    .cutBlind(-2)\n",
    ")\n",
    "\n",
    "show(case)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## USB connector\n",
    "Make a slot for the USB connector.   \n",
    "The USB connector needs to be drilled on the rear side, so the reference plane is the furthest plane on the Y-axis.   \n",
    "There are planes with the same distance in front and behind, however, the plane selected is the rear side, so it is left as it is."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# USB connector slot location and dimensions\n",
    "USB_POS = (11.25, 1.2 + 3.15 / 2)\n",
    "USB_HOLE_SIZE = [9, 3.2, 7.5]\n",
    "USB_HOLE_MARGIN = 0.5\n",
    "USB_CONN_SIZE = (11, 8)\n",
    "\n",
    "# Base on the furthest surface in the Y-axis direction\n",
    "# Cut out the USB connector slot deep enough to the inside to avoid hitting the USB connector\n",
    "# Cut out areas to avoid the USB cable housing\n",
    "case = (\n",
    "    case.faces(\">Y\")\n",
    "    .workplane(centerOption=\"CenterOfMass\")\n",
    "    .center(\n",
    "        PCB_WIDTH / 2 - USB_POS[0],\n",
    "        CASE_HEIGHT / 2 - CASE_MARGIN_TOP - PCB_THICKNESS - USB_POS[1],\n",
    "    )\n",
    "    .tag(\"USBCutout\")\n",
    "    .rect(USB_HOLE_SIZE[0] + USB_HOLE_MARGIN, USB_HOLE_SIZE[1] + USB_HOLE_MARGIN)\n",
    "    .cutBlind(-(CASE_FRAME + USB_HOLE_SIZE[2] + 2))\n",
    "    .workplaneFromTagged(\"USBCutout\")\n",
    "    .rect(USB_CONN_SIZE[0], USB_CONN_SIZE[1])\n",
    "    .cutBlind(-1)\n",
    ")\n",
    "\n",
    "show(case)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Cover under OLED\n",
    "Create a cover to be placed between the PCB and the OLED.   \n",
    "The cover will be made as a separate object, but its position will be aligned.\n",
    "The cover will be fixed with double-sided tape, so no screws are required."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "OLED_COVER_WIDTH = 19\n",
    "OLED_COVER_HEIGHT = 19 * 2 - 3\n",
    "OLED_COVER_THICKNESS = 10\n",
    "\n",
    "# Base on offset plane from the XY plane by the height above the PCB.\n",
    "oled_cover = (\n",
    "    cq.Workplane(\"XY\")\n",
    "    .workplane(offset=CASE_BOTTOM + CASE_MARGIN_BOTTOM + PCB_THICKNESS)\n",
    "    .center(-PCB_WIDTH / 2, PCB_HEIGHT / 2)\n",
    "    .tag(\"PCB_ORIGIN\")\n",
    "    .center(OLED_COVER_WIDTH / 2, -OLED_COVER_HEIGHT / 2)\n",
    "    .rect(OLED_COVER_WIDTH, OLED_COVER_HEIGHT)\n",
    "    .extrude(OLED_COVER_THICKNESS)\n",
    "    .faces(\"Z\")\n",
    "    .tag(\"CASE_TOP\")\n",
    "    .edges(\">Y or <X\")\n",
    "    .chamfer(1)\n",
    ")\n",
    "\n",
    "show(case, oled_cover)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Exporting 3D data\n",
    "Finally, export the 3D model in a data format that can be 3D printed."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "cq.exporters.export(case, \"case.stl\")\n",
    "cq.exporters.export(oled_cover, \"oled_cover.stl\")"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Print the case\n",
    "\n",
    "Print the stl file generated.\n",
    "\n",
    "![slicer](../imgs/slicer.png)\n",
    "\n",
    "![PCB and case](../imgs/case.jpg)\n",
    "\n",
    "![Connector](../imgs/case_usb.jpg)\n",
    "\n",
    "After printing, I put the PCB on the case and checked it.\n",
    "The connector position is perfect.\n",
    "\n",
    "The case is now complete!"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.10 (default, Jun 22 2022, 20:18:18) \n[GCC 9.4.0]"
  },
  "vscode": {
   "interpreter": {
    "hash": "b68c839e49670d3aa77ea8bdd6f541fdcce81585b85d6e1d97674ad5f32db92f"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
